package user

import (
	"encoding/binary"
	"errors"
	"fmt"
	"log"
	"reflect"

	"github.com/golang/protobuf/proto"
	"pac/crc"
	"pac/packet"
	"pac/proto"
)

type Packet struct {
	// 协议包版本
	Ver int

	// 包序列号
	Seq Seq

	// FLAGS
	Flats uint8

	// 方向
	Dir Dir

	// 命令
	Cmd Cmd

	// 数据
	Payload []byte

	// 原始数据
	Raw []byte

	// protoBuf 数据
	Pb proto.Message
	// Unmarshal protoBuf 时的错误信息
	ErrOfUnmarshalProtoBuf error
}

// 报序列号
type Seq [4]byte

func (seq Seq) String() string {
	return fmt.Sprintf("%x", seq[:])
}

// 包方向
type Dir bool

const (
	// Req 请求方向
	Req Dir = false
	// Rsp 回响方向
	Rsp Dir = true
)

func (dir Dir) String() string {
	if dir == Req {
		return "REQ"
	}
	return "RSP"
}

// 命令
type Cmd pb.Cmd

func (cmd *Cmd) IsCmd() {}

func (cmd *Cmd) String() string {
	if cmd == nil {
		return "<nil>"
	}
	return pb.Cmd(*cmd).String()
}

func (cmd *Cmd) Equal(cmd2 packet.Cmd) bool {
	if c, ok := cmd2.(*Cmd); ok {
		return *cmd == *c
	}
	return false
}

func (p *Packet) String() string {
	if p == nil {
		return ""
	}
	var pbInfo string
	if p.Pb == nil {
		pbInfo = "?"
	} else {
		pbInfo = p.Pb.String()
		if len(pbInfo) > 1024 {
			pbInfo = ""
			if p.IsResponse() {
				v := reflect.ValueOf(p.Pb).Elem().FieldByName("ErrCode")
				if v.IsValid() {
					pbInfo = v.Interface().(pb.ErrCode).String()
				}
			}
		}
	}
	return fmt.Sprintf("%d,%v,%v,%v %v", p.Ver, p.Seq, p.Dir, p.CmdStr(), pbInfo)
}

func (p *Packet) GetVer() int {
	return p.Ver
}

func (p *Packet) GetSeq() Seq {
	return p.Seq
}

func (p *Packet) Bytes() int {
	if p == nil {
		return 0
	}
	return len(p.Raw)
}

func (p *Packet) GetCmd() packet.Cmd {
	return &p.Cmd
}

func (p *Packet) CmdStr() string {
	return p.Cmd.String()
}

func (p *Packet) IsRequest() bool {
	return p.GetDir() == Req
}

func (p *Packet) IsResponse() bool {
	return p.GetDir() == Rsp
}

func (p *Packet) GetFlags() uint8 {
	return p.Flats
}

func (p *Packet) GetDir() Dir {
	return p.Dir
}

func (p *Packet) GetPayload() []byte {
	return p.Payload
}

func (p *Packet) GetRaw() []byte {
	return p.Raw
}

const (
	Syn byte = 0x16

	IdxSyn   = 0
	LenSyn   = 1
	IdxVer   = IdxSyn + LenSyn
	LenVer   = 1
	IdxLen   = IdxVer + LenVer
	LenLen   = 4
	IdxSeq   = IdxLen + LenLen
	LenSeq   = 4
	IdxFlags = IdxSeq + LenSeq
	LenFlags = 1
	IdxCmd   = IdxFlags + LenFlags
	LenCmd   = 2
	IdxData  = IdxCmd + LenCmd
	LenCrc   = 1

	LenMin = LenSyn + LenVer + LenLen + LenSeq + LenFlags + LenCmd + LenCrc
)

func (p *Packet) Marshal() ([]byte, error) {
	if p == nil {
		return nil, errors.New("BasePacket.Marshal: packet is nil")
	}

	payloadLen := len(p.Payload)
	pktLen := LenMin + payloadLen

	raw := make([]byte, pktLen)

	raw[IdxSyn] = Syn
	raw[IdxVer] = byte(p.Ver)
	binary.LittleEndian.PutUint32(raw[IdxLen:IdxLen+LenLen], uint32(pktLen))
	copy(raw[IdxSeq:IdxSeq+LenSeq], p.Seq[:])
	if p.Dir == Req {
		p.Flats &^= 0x01
	} else {
		p.Flats |= 0x01
	}
	raw[IdxFlags] = p.Flats
	binary.LittleEndian.PutUint16(raw[IdxCmd:IdxCmd+LenCmd], uint16(p.Cmd))
	if payloadLen != 0 {
		copy(raw[IdxData:IdxData+payloadLen], p.Payload)
	}
	raw[pktLen-1] = crc.Checksum(raw[:pktLen-1])

	p.Raw = raw

	return raw, nil
}

var ErrNeedMoreData = errors.New("packet: need more data")
var ErrCrc = errors.New("packet: CRC error")

func decode(dat []byte) (pkt *Packet, remain []byte, err error) {
	//  +--------+--------+---------+---------+--------+---------+---------+--------+
	//  | SYN    | VER    | LEN     | SEQ     | FLAGS  | CMD     | DATA    | CRC    |
	//	+--------+--------+---------+---------+--------+---------+---------+--------+
	//  | 1 byte | 1 byte | 4 bytes | 4 bytes | 1 byte | 2 bytes | n bytes | 1 byte |
	//	+--------+--------+---------+---------+--------+---------+---------+--------+

	if len(dat) < 1 {
		return nil, dat, ErrNeedMoreData
	}
	if dat[0] != Syn {
		return nil, dat, fmt.Errorf("packet.DePacket: invalid SYN (%x)", dat[0])
	}

	if len(dat) < IdxVer+LenVer {
		return nil, dat, ErrNeedMoreData
	}
	if dat[IdxVer] != 0 {
		return nil, dat, fmt.Errorf("packet.DePacket: invalid VER (%x)", dat[0])
	}

	if len(dat) < IdxLen+LenLen {
		return nil, dat, ErrNeedMoreData
	}
	var length int
	length = int(binary.LittleEndian.Uint32(dat[IdxLen : IdxLen+4]))
	if length < LenMin {
		return nil, dat, fmt.Errorf("packet.DePacket: length too sort (%d)", length)
	}

	if len(dat) < IdxFlags+LenFlags {
		return nil, dat, ErrNeedMoreData
	}

	if dat[IdxFlags]&0xFE != 0 {
		return nil, dat, fmt.Errorf("packet.DePacket: invalid FLAGS (%x)", dat[IdxFlags])
	}

	if len(dat) < length {
		return nil, dat, ErrNeedMoreData
	}

	if crc.Checksum(dat[:length-1]) != dat[length-1] {
		return nil, dat, ErrCrc
	}

	pkt = &Packet{}

	pkt.Ver = int(dat[IdxVer])

	copy(pkt.Seq[:], dat[IdxSeq:IdxSeq+LenSeq])

	pkt.Flats = dat[IdxFlags]

	if pkt.Flats&0x01 == 0 {
		pkt.Dir = Req
	} else {
		pkt.Dir = Rsp
	}

	pkt.Cmd = Cmd(binary.LittleEndian.Uint16(dat[IdxCmd : IdxCmd+LenCmd]))

	pkt.Payload = make([]byte, length-LenMin)
	copy(pkt.Payload, dat[IdxData:IdxData+len(pkt.Payload)])

	return pkt, dat[length:], nil
}

func newPacket() (pkt *Packet) {
	return &Packet{
		Ver:   0,
		Flats: 0,
	}
}

func newPacketByPb(seq Seq, pbMsg proto.Message) (pkt *Packet, err error) {
	var (
		payload []byte
		cmd     Cmd
		dir     Dir
	)

	switch pbMsg.(type) {
	case *pb.CommonRsp:
		payload, err = proto.Marshal(pbMsg)
		if err != nil {
			return nil, err
		}
		dir = Rsp

	default:
		cmd, dir, err = calcCmdAndDir(pbMsg)
		if err != nil {
			return nil, err
		}
	}

	payload, err = proto.Marshal(pbMsg)
	if err != nil {
		return nil, err
	}

	pkt = newPacket()
	pkt.Seq = seq
	pkt.Dir = dir
	pkt.Cmd = cmd
	pkt.Payload = payload
	pkt.Raw, err = pkt.Marshal()
	if err != nil {
		return nil, err
	}
	pkt.Pb = pbMsg
	return pkt, nil
}

func (p *Packet) unmarshalProtoBuf() error {
	cmd := pb.Cmd(p.Cmd)
	dir := p.Dir
	mt := pbMsgType(cmd, dir)
	if mt == nil {
		p.ErrOfUnmarshalProtoBuf = fmt.Errorf("packet: unmarshalProtoBuf: unexpected cmd %v and dir %v", cmd, dir)
	} else {
		p.Pb = reflect.New(mt.Elem()).Interface().(proto.Message)
		err := proto.Unmarshal(p.Payload, p.Pb)
		if err != nil {
			p.Pb = nil
			p.ErrOfUnmarshalProtoBuf = fmt.Errorf("packet: unmarshalProtoBuf: for cmd %v: %s", cmd, err.Error())
		} else {
			p.ErrOfUnmarshalProtoBuf = nil
		}
	}
	return p.ErrOfUnmarshalProtoBuf
}

func DePacket(dat []byte) (pkt *Packet, remain []byte, err error) {
	pkt, remain, err = decode(dat)
	if err != nil || pkt == nil {
		return nil, dat, err
	}

	pkt.unmarshalProtoBuf()

	return pkt, remain, nil
}

var (
	pbMsgMapTypes = make(map[uint64]reflect.Type)
	revPbMsgTypes = make(map[reflect.Type]uint64)
)

func pbMsgType(cmd pb.Cmd, dir Dir) reflect.Type {
	return pbMsgMapTypes[cmdDir2Key(cmd, dir)]
}

func cmdDir2Key(cmd pb.Cmd, dir Dir) (key uint64) {
	var dirInt uint64
	if dir == Rsp {
		dirInt = 1
	}
	return (uint64(cmd) << 8) | dirInt
}

func key2CmdDir(key uint64) (cmd pb.Cmd, dir Dir) {
	dirInt := key & 0xFF
	if dirInt == 0 {
		dir = Req
	} else {
		dir = Rsp
	}
	cmd = pb.Cmd(key >> 8)
	return
}

func calcCmdAndDir(pbMsg proto.Message) (cmd Cmd, dir Dir, err error) {
	mt := reflect.TypeOf(pbMsg)
	key, ok := revPbMsgTypes[mt]
	if ok {
		pbCmd, dir := key2CmdDir(key)
		return Cmd(pbCmd), dir, nil
	}
	return Cmd(0xFFFF), dir, fmt.Errorf("packet: calcCmdAndDir: unexpected proto message type \"%s\"", mt.String())
}

func registerPbMsg(cmd pb.Cmd, dir Dir, pbMsg proto.Message) {
	key := cmdDir2Key(cmd, dir)

	if _, ok := pbMsgMapTypes[key]; ok {
		log.Printf("packet: duplicate proto message registered: %s %s: %T", cmd.String(), dir.String(), pbMsg)
		return
	}

	t := reflect.TypeOf(pbMsg)
	pbMsgMapTypes[key] = t
	revPbMsgTypes[t] = key
}

var packetProcessorMap = make(map[uint64]func(*Client, *Packet))

func registerPbMsgAndProcessor(cmd pb.Cmd, dir Dir, pbMsg proto.Message, processor func(*Client, *Packet)) {
	registerPbMsg(cmd, dir, pbMsg)
	packetProcessorMap[cmdDir2Key(cmd, dir)] = processor
}
